package org.wzy.spring.deprecated.manually.servlet;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.wzy.spring.deprecated.manually.annotation.Autowired;
import org.wzy.spring.deprecated.manually.annotation.Controller;
import org.wzy.spring.deprecated.manually.annotation.Service;
import org.wzy.spring.deprecated.mvc.demo.controller.DemoController;
import org.wzy.spring.deprecated.mvc.demo.controller.DemoController1;

import javax.servlet.ServletConfig;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * DispatcherServlet, 用于实现v1.0版本Spring IOC容器
 *
 * @author wzy_h
 * @version v_1.0
 * @date 2018/5/19 16:16
 * @since 1.8
 * @deprecated See {@code org.wzy.spring.framework.webmvc.servlet.DispatcherServlet}.
 */
@Deprecated
public class DispatcherServlet extends HttpServlet {

    /**
     * 用于存储用户自定义properties文件内容(模拟xml)
     */
    private final static Properties CONTEXT_CONFIGURATION = new Properties();
    /**
     * 用于存储注册后的 Bean(简版IOC容器实现)
     */
    private final static Map<String, Object> BEANS_MAP = new ConcurrentHashMap<>(10);
    /**
     * 用于存储读取到的 class 名称, 如 org.wzy.spring.deprecated.mvc.demo.controller.DemoController
     */
    private final static List<String> CLASS_NAMES = new ArrayList<>(10);
    private Logger logger = LoggerFactory.getLogger(DispatcherServlet.class);

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
        logger.info("DispatcherServlet 调用了 doGet 方法");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
        logger.info("DispatcherServlet 调用了 doPost 方法");
    }

    /**
     * Servlet 初始化方法, 所有有关 Spring IOC 容器的初始化、依赖注入过程都是在此方法中完成
     *
     * @author wzy_h
     * @date 2018/5/19 16:28
     * @since 1.8
     */
    @Override
    public void init(ServletConfig config) {
        // 开始初始化的进程
        /* 1. 定位, 实际上就是读取配置文件*/
        doLoadConfigurations();

        /* 2. 加载, 将配置文件中的内容读取出来 */
        try {
            doScan(CONTEXT_CONFIGURATION.getProperty("basePackage"));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }

        /* 3.注册, 将读取后的配置文件内容注册到IOC容器中 */
        doRegister();

        /* 4.自动依赖注入 */
        /* 在 Spring 中是通过调用 getBean 方法触发依赖注入的 */
        doAutowired();

        /* 注入完成之后的测试, 打印的 service 所在内存地址应该一样, 并且若注入失败的话调用 service 方法会出现异常 */
        DemoController demoController = (DemoController) BEANS_MAP.get("demoController");
        demoController.test("我是张三!!!");
        DemoController1 demoController1 = (DemoController1) BEANS_MAP.get("demoController1");
        demoController1.test("我是李四!!!");

        // Spring MVC 需要一个 HandlerMapping
        initHandlerMapping();
    }

    /**
     * 定位配置文件
     * 在Spring中是通过 Reader 去查找和定位的
     *
     * @author wzy_h
     * @date 2018/5/19 13:20
     * @since 1.8
     */
    private void doLoadConfigurations() {

        // 使用输入流, 读取 properties 文件的内容
        InputStream resource = this.getClass().getClassLoader().getResourceAsStream("application.properties");
        try {
            CONTEXT_CONFIGURATION.load(resource);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != resource) {
                try {
                    resource.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 加载 Bean
     * 实际上就是扫描指定包下包含指定注解的 Bean
     *
     * @param packageName 包名
     * @author wzy_h
     * @date 2018/5/19 13:21
     * @since 1.8
     */
    private void doScan(String packageName) throws FileNotFoundException {
        // 简略实现, 直接读取指定包下的所有文件
        URL url = this.getClass().getClassLoader().getResource("/" + packageName.replaceAll("\\.", "/"));
        if (null == url) {
            throw new FileNotFoundException("未找到指定路径！");
        }

        File urlFile = new File(url.getFile());
        for (File file : Objects.requireNonNull(urlFile.listFiles())) {
            if (file.isDirectory()) {
                // 如果找到的是文件夹, 则递归调用本方法
                logger.info("doScan() 找到了文件夹, 将继续递归查找... packageName = {}", packageName + "." + file.getName());
                doScan(packageName + "." + file.getName());
            } else {
                logger.info("doScan() 找到了文件, 准备添加至集合中... packageName = {}", packageName + "." + file.getName());
                CLASS_NAMES.add(packageName + "." + file.getName().replace(".class", ""));
            }
        }
    }

    /**
     * 注册 Bean 至 IOC 容器
     *
     * @author wzy_h
     * @date 2018/5/19 13:22
     * @since 1.8
     */
    private void doRegister() {
        if (CLASS_NAMES.isEmpty()) {
            logger.error("doRegister() -> 未找到指定Bean");
            return;
        }

        for (String className : CLASS_NAMES) {
            try {
                Class<?> clazz = Class.forName(className);

                // 在 Spring 中使用了策略模式运用多个子方法来处理的
                if (clazz.isAnnotationPresent(Controller.class)) {
                    // Spring 在这个阶段是不会直接 put 一个 instance, 而是 BeanDefinition
                    BEANS_MAP.put(lowerFirstChar(clazz.getSimpleName()), clazz.newInstance());
                } else if (clazz.isAnnotationPresent(Service.class)) {
                    Service service = clazz.getAnnotation(Service.class);
                    // 在 Spring 中默认使用的是类型注入
                    // 在本示例中, 默认使用类名首字母注入, 如果自己定义了 beanName, 优先使用自定义 beanName
                    // 如果是一个接口, 则使用接口的类型去自动注入

                    /* 在 Spring 中同样会分别调用不同的方法 */
                    /* 详见 AbstractAutowireCapableBeanFactory#populateBean -> autowireByName / autowireByType */
                    String specifiedBeanName = service.value();
                    if ("".equals(specifiedBeanName.trim())) {
                        specifiedBeanName = lowerFirstChar(clazz.getSimpleName());
                    }

                    Object serviceInstance = clazz.newInstance();
                    // 保证同一个 name 在 IOC 容器中只存在一份(简化版)
                    BEANS_MAP.putIfAbsent(specifiedBeanName, serviceInstance);

                    // 如果该 Bean 实现了接口, 则保留该接口以便后面属性的自动注入(简化版, 实际上未必实现的接口中的方法会被调用)
                    Class<?>[] clazzInterfaces = clazz.getInterfaces();
                    for (Class<?> clz : clazzInterfaces) {
                        BEANS_MAP.putIfAbsent(clz.getName(), serviceInstance);
                    }
                }
            } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 触发自动注入
     *
     * @author wzy_h
     * @date 2018/5/19 13:22
     * @since 1.8
     */
    private void doAutowired() {
        if (BEANS_MAP.isEmpty()) {
            return;
        }

        for (Map.Entry<String, Object> entry : BEANS_MAP.entrySet()) {
            // 只要加了 @Autowired 的 Field 都会被自动注入
            Field[] declaredFields = entry.getValue().getClass().getDeclaredFields();
            for (Field field : declaredFields) {
                if (field.isAnnotationPresent(Autowired.class)) {
                    Autowired annotation = field.getAnnotation(Autowired.class);
                    String beanName = annotation.value().trim();
                    if ("".equals(beanName)) {
                        beanName = field.getType().getName();
                    }
                    // 若field被声明为 private, 需要获取访问权限
                    field.setAccessible(true);
                    try {
                        field.set(entry.getValue(), BEANS_MAP.get(beanName));
                    } catch (IllegalAccessException e) {
                        logger.error("Reflect field {} failed!", beanName);
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    /**
     * 将 @RequestMapping 中配置的 url 和一个方法关联上
     * 以便于从浏览器获得用户输入的 url 之后能够找到具体执行的方法, 并通过反射调用该方法
     *
     * @author wzy_h
     * @date 2018/5/19 13:23
     * @since 1.8
     */
    private void initHandlerMapping() {
    }

    /**
     * 将给定字符串首字母转换为小写
     *
     * @author wzy_h
     * @date 2018/5/19 16:03
     * @since 1.8
     */
    private String lowerFirstChar(String originalStr) {
        if (null == originalStr || "".equals(originalStr)) {
            return originalStr;
        }

        char[] array = originalStr.toCharArray();
        array[0] += 32;
        return String.valueOf(array);
    }
}
